Skip to main content

Mouse and keyboard

In this section, we discuss ways in which a user can interact with dynamic elements, providing different types of input.

UI Elements

A core package wljs-inputs provides a set of basic UI elements used for creating buttons, sliders, text-fields and etc.

info

If you need a custom element, you can create it right from the notebook using WLX or Javascript cell types. Please see the guide Emitting events

The following elements are available from out of the box:

and for grouping elements

Examples

Each standard input element is EventObject, to which you can assign any handler function. You don't necessarily need to assign it to a variable, i.e. (see InputButton)

EventHandler[InputButton["Click"], Beep]

is fine as well as

btn = InputButton["Click"];
EventHandler[btn, Beep];

btn

Here is some other examples

EventHandler[InputRange[0,1,0.1], Function[value, radius = value]]
% // EventFire; (* just to initialize `radius` *)

Graphics[{LightBlue, Disk[], Pink, Disk[{0,0}, radius // Offload]}]

tip

Apply EventFire on any EventObject to manually fire an event with a default value to initialize your variables (if needed).

You can also add a label to a InputRange

InputRange[0,1,0.1, "Label"->"Radius"]

and initial value as a third argument

InputRange[0,1,0.1, 0.7, "Label"->"Radius"]

Here is an example on InputSelect

angle = 45 Degree;
EventHandler[InputSelect[{Pi/2 -> "90", Pi/4 -> "45", 0 -> "0"}, Pi/4], Function[value, angle = value]]

Graphics[{Rotate[Rectangle[{0,0}, {1,1}], angle // Offload]}]

Here is a simple text-input

text = "Example";
EventHandler[InputText[], Function[value, text = value]]

Graphics[Table[{
Hue[i/10., 1.,1.], Rotate[Text[Style[text // Offload, FontSize->RandomInteger[{12,24}]], RandomReal[{-1,1}, 2]], RandomChoice[{Pi, Pi/4, Pi/2, 0}]]
}, {i, 10}]]

Grouping input elements

If you need to make just visually, then consider to use Grid, [[Row]] or Column, i.e.

slider = InputRange[0,1,0.1]; 
button = InputButton[];

{slider, button} // Column

There is another way of grouping on the level of events using InputGroup

slider = InputRange[0,1,0.1]; 
button = InputButton[];

joined = InputGroup[<|"Button"->button, "Slider"->slider|>, "Label"->"Group"];
EventHandler[joined, Print]

It merges an association (as in example above) or list of EventObjects into a new one. You do not need to assign separate EventHandler for each, instead you need only one joined. It fires an event keeping the original form of used association or list

payload
<|"Slider"->0.5, "Button"->True|>

Join different events

One can also merge event objects underneath of UI elements using Join. Let us have a look at the simples example

button = InputButton[]
slider = InputRange[0,1,0.1]

EventHandler[Join[button, slider], Function[data,
Print[data]
]];

As a result you will get something like this

True

or

0.5

depending which element it generated. In order to resolve this issue, one can utilize patterns (or topics see EventObject)

button = InputButton["Topic"->"Button"]
slider = InputRange[0,1,0.1, "Topic"->"Slider"]

EventHandler[Join[button, slider], {type_ :> Function[data,
Print[type<>":"<>ToString[data]]
]}];

or capture them individually

button = InputButton["Topic"->"Button"]
slider = InputRange[0,1,0.1, "Topic"->"Slider"]

EventHandler[Join[button, slider], {
"Button" -> Beep,
"Slider" -> Print
}];

A slider will print a message, while a button will make beep sound.

Chaining events

Most of GUI elements do support chaining, when each of them reuse the same EventObject. It comes as a first argument

ev = EventObject[];

InputButton[ev, "Topic"->"Button"]
InputRange[ev, 0,1,0.1, "Topic"->"Slider"]

EventHandler[ev, {
"Button" -> Beep,
"Slider" -> Print
}];

In such case, there is no need in creating new events and joining them. In the end this approach leaves less footprint as well as less overhead to the system.

2D Graphics

Some of the primitives as well as entire canvas support EventHandler method. Let us start with a canvas - Graphics`Canvas[]

Canvas

You can attach event handlers to Graphics`Canvas object, which represents the given SVG container of your 2D graph inside Graphics.

It has some benefits compared to Primitives, namely "mousemove" or "click" will be captured even if there are some objects on the front. The following patterns (topics of EventHandler) are supported

  • "keydown" will capture the focus of the window once a user click on it
  • "capturekeydown" will capture the focus of the window and prevent page scrolling
  • "mousemove" will capture a mouse position
  • "click" captures clicks and sends the position (no alt key pressed)
  • "altclick" captures clicks (with held alt key)

For example

pt = {};
Graphics[{
PointSize[0.05], Blue, Opacity[0.5],
Point[pt // Offload],
EventHandler[Graphics`Canvas[], {
"mousemove" -> Function[xy, pt = Append[pt, xy]]
}]
}]

Primitives

For some of graphics primitives it is possible to attach EventHandler as well. The following symbols are supported

which can accept the following pattern to capture events

  • "drag" make a primitive draggable and sends coordinates
  • "dragall" the same as previous, but submits events, when dragging was initiated and finished as well
  • "click" sends coordinates, where a click was captured (no held Alt key)
  • "altclick" the same as previous, but with held Alt key
  • "mousedown" captured event on press and sends the coordinates
  • "mouseup" captures event on release
  • "mousemove" captures mouse position
  • "mouseover" captures mouse position once it is entered the element's area
  • "zoom" captures mouse wheel

For example, combining "zoom" and "drag", one perform a manual fitting of some gaussian curves

try to move a mouse wheel on a red dot and then drag it

In principle using a large white rectangle with mousemove pattern, once can build a simple mouse follower

pt = {0,0};
Graphics[{
White,
EventHandler[
Rectangle[{-2,-2}, {2,2}],
{"mousemove"->Function[xy, pt = xy]}
],
PointSize[0.05], Cyan,
Point[pt // Offload]
}]

a mouse follower

3D Graphics

For now event listeners in Graphics3D are quite limited.

Primitives

The following primitives support EventHandler methods

which can be used with patterns

  • "transform" makes an object draggable and sends an association with "position" field

For example it comes handy while working with dynamic lighting system

point = {1,1,1};

Graphics3D[{Shadows[True],
Polygon[{{-5, 5, -1}, {5, 5, -1}, {5, -5, -1}, {-5, -5, -1}}], White,
Cuboid[{-1, -1, -1}, {1, 1, 1}], Shadows[False],
PointLight[Red, {1.5075, 4.1557, 2.6129}, 100],
Shadows[True], SpotLight[Cyan, point // Offload]

EventHandler[Sphere[point, 0.1], {
"transform" -> Function[assoc, point = assoc["position"]]
}]

}, "Lighting" -> None]